package me.buick.util.jmeter.snmpprocess.sampler;

import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.PORT;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SERVER;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SERVER_TYPE;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SNMP_DATA_LOG_MODE;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SNMP_DATA_LOG_PATH;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SNMP_DATA_RETRIEVE_INTL;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SNMP_PROC_DESCR;
import static me.buick.util.jmeter.snmpprocess.SNMPProcessSamplerConstants.SNMP_SUITE_ID;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;

import me.buick.util.jmeter.snmpprocess.snmpmgmt.SNMPProcessSampleResult;
import me.buick.util.jmeter.snmpprocess.snmpmgmt.SNMPProcessSampleResultProcessor;

import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;

/**
 * 
 * Version 1.0
 * 
 * 
 * 
 * @author buick
 *
 */

public class SNMPProcessSampler extends AbstractSampler implements TestListener{

	private static final long serialVersionUID = -7290858820871521294L;

	static final Logger log = LoggingManager.getLoggerForClass();
	
	// thread register used for recording all the started sampler thread. when the tests are stopped, used these recording to close remote threads
	static final Map<Thread, SNMPProcessSampleResultProcessor> threadRegister = Collections.synchronizedMap(new HashMap<Thread, SNMPProcessSampleResultProcessor>());
	
	// thread register used for recording all the opening output/input stream. when the tests are stopped, used these recording to close these file stream
	static final Map<Thread, Writer> outputStreamRegister = Collections.synchronizedMap(new HashMap<Thread, Writer>());
	
	static final Map<Thread, RandomAccessFile> inputStreamRegister = Collections.synchronizedMap(new HashMap<Thread, RandomAccessFile>());

	SNMPProcessSampleResultProcessor processor;

	String server;
	String port;
	String serverType;
	String snmpSuiteID;
	String procDescr;
	String snmpDataRetriveInterval;
	String snmpDataLogPath;
	String snmpDataLogMode;
	
	static Writer fileOut;
	RandomAccessFile  fileIn;
	static PrettyPrintWriter ppw;
	static final Object LOCK = new Object();
	
	static final AtomicInteger COUNTER = new AtomicInteger(1);
	
	static final XStream xs;
	
	// XStream is used here for recording result to file or load the data from file
	// here some instance / class members are omitted because they are useless in recording / loading process.
	static {
		xs = new XStream(new DomDriver());
		xs.omitField(SNMPProcessSampleResult.class, "processor");
		xs.omitField(SNMPProcessSampleResult.class, "preSampleResult");
		xs.omitField(SNMPProcessSampleResult.class, "log");
		xs.omitField(SNMPProcessSampleResult.class, "startTime");
		xs.omitField(SNMPProcessSampleResult.class, "endTime");
	}
	
	static AtomicBoolean initChecked = new AtomicBoolean(false);
	boolean isSave		= false;
	boolean isLoad		= false;
	
	long offset = 0;
	
	StringBuffer sb;
	
	public String getSnmpDataLogPath() {
		return this.getPropertyAsString(SNMP_DATA_LOG_PATH);
	}

	public void setSnmpDataLogPath(String snmpDataLogPath) {
		this.setProperty(SNMP_DATA_LOG_PATH, snmpDataLogPath);
	}

	public String getSnmpDataLogMode() {
		return this.getPropertyAsString(SNMP_DATA_LOG_MODE);
	}

	public void setSnmpDataLogMode(String snmpDataLogMode) {
		this.setProperty(SNMP_DATA_LOG_MODE, snmpDataLogMode);
	}

	public SNMPProcessSampleResultProcessor getSampleResultProcessor(){
		return this.processor;
	}
	
	public String getSnmpDataRetrieveInterval() {
		return this.getPropertyAsString(SNMP_DATA_RETRIEVE_INTL);
	}

	public void setSnmpDataRetrieveInterval(String snmpDataRetriveInterval) {
		this.setProperty(SNMP_DATA_RETRIEVE_INTL, snmpDataRetriveInterval);
	}

	public String getServer() {
		return this.getPropertyAsString(SERVER);
	}

	public void setServer(String server) {
		this.setProperty(SERVER, server);
	}

	public String getPort() {
		return this.getPropertyAsString(PORT);
	}

	public void setPort(String port) {
		this.setProperty(PORT, port);
	}

	public String getServerType() {
		return this.getPropertyAsString(SERVER_TYPE);
	}

	public void setServerType(String serverType) {
		this.setProperty(SERVER_TYPE, serverType);
	}

	public String getSnmpSuiteID() {
		return this.getPropertyAsString(SNMP_SUITE_ID);
	}

	public void setSnmpSuiteID(String snmpSuiteID) {
		this.setProperty(SNMP_SUITE_ID, snmpSuiteID);
	}
	
	public String getProcDescr() {
		return this.getPropertyAsString(SNMP_PROC_DESCR);
	}

	public void setProcDescr(String proc) {
		this.setProperty(SNMP_PROC_DESCR, proc);
	}
	
	// seed the result in time order
	synchronized static final long timestampSeed(){
		return System.currentTimeMillis();
	}

	public SNMPProcessSampler() {
	}
	
	/**
	 * 
	 * This method is a override method which inherited from super class, and this is also the method required by the JMeter Architecture.
	 * When you start SNMP Process Sampler in JMeter, these method will be invoked in every JMeter interval
	 * 
	 * This method mainly focuses on three tasks:
	 * 
	 * 1: Load the historical data file to generated the performance graph.
	 * 
	 * 2: Save the current performance data to file
	 * 
	 * 3: Execute the performance monitor task
	 * 
	 */
	public SampleResult sample(Entry e){
		SNMPProcessSampleResult res = null;
		String dm = this.getSnmpDataLogMode();
		String logPath = this.getSnmpDataLogPath();
		if(!(initChecked.getAndSet(true))){
			this.isLoad = (dm != null && dm.equalsIgnoreCase("Load"));
			this.isSave = (dm != null && dm.equalsIgnoreCase("Save"));
			if(this.isLoad){
//				String logPath = this.getSnmpDataLogPath();
				if(logPath != null && logPath.trim().length() > 0){
					try {
						if(this.fileIn == null){
							fileIn = new RandomAccessFile(logPath,"r");
							inputStreamRegister.put(Thread.currentThread(), fileIn);
						}
					} catch (FileNotFoundException e1) {
						e1.printStackTrace();
						return null;
					}
				}else{
					return null;
				}
			}else if(this.isSave){
//				String logPath = this.getSnmpDataLogPath();
				if(logPath != null && logPath.trim().length() > 0){
					try {
						if(fileOut == null){
							fileOut = new BufferedWriter(new FileWriter(logPath,true));
							ppw = new PrettyPrintWriter(fileOut);
							outputStreamRegister.put(Thread.currentThread(), fileOut);
						}
					} catch (Exception e1) {
						e1.printStackTrace();
						return null;
					}
				}
			}
			//initChecked = true;
		}
		if(isLoad){
			res = parseSampleResultFromFile();
			if(res == null){
				this.getThreadContext().getThread().stop();
			}
			res.sampleStart();
			res.sampleEnd();
			return res;
		}else{
			if (processor == null) {
				server = getServer();
				port = getPort();
				snmpSuiteID = getSnmpSuiteID();
				serverType = getServerType();
				snmpDataRetriveInterval = getSnmpDataRetrieveInterval();
				processor = new SNMPProcessSampleResultProcessor(server, port,
						snmpSuiteID, serverType,snmpDataRetriveInterval);
				threadRegister.put(Thread.currentThread(), processor);
			}

			res = new SNMPProcessSampleResult(processor);
			res.setProcDescr(getProcDescr());
			res.setSnmpDataRetriveInterval(snmpDataRetriveInterval);
			
			try {
				res.sampleStart();
//				res.setTimeStamp(System.currentTimeMillis());
				res.setTimeStamp(timestampSeed());
				res.setURL(new URL("http://" + getServer() + ":" + getPort() + "/"));
				res.processSampleResult();
				res.setResponseCodeOK();
				res.setResponseMessage("OK");
				res.sampleEnd();
				synchronized(LOCK){
					if(this.isSave&&(logPath != null && logPath.trim().length() > 0)){
						xs.toXML(res, fileOut);
						fileOut.write("\r\n"+"${cnt"+(COUNTER.getAndIncrement())+"}"+"\r\n");
					}
				}
				return res;
			} catch (Exception err) {
				err.printStackTrace();
			}
		}
		return null;
	}
	
	/**
	 * This is a method used to receive the test end event which required by TestListener interface
	 * 
	 * When the user cilck stop in the menu, this method will be invoked.
	 * 
	 * This method will close remote SNMP process data fetcher threads and close the
	 * data recording input/output stream
	 * 
	 * @see TestListener
	 */
	public void testEnded() {
		
		if(threadRegister != null && threadRegister.size() > 0){
			for(Map.Entry<Thread, SNMPProcessSampleResultProcessor> entry : threadRegister.entrySet()){
				entry.getValue().getAdaptor().discardDAOs();
			}
		}
		threadRegister.clear();
		
		if(outputStreamRegister != null && outputStreamRegister.size() > 0){
			for(Map.Entry<Thread, Writer> entry : outputStreamRegister.entrySet()){
				Writer o = entry.getValue();
				try {
					o.flush();
					o.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		outputStreamRegister.clear();
		
		if(inputStreamRegister != null && inputStreamRegister.size() > 0){
			for(Map.Entry<Thread, RandomAccessFile> entry : inputStreamRegister.entrySet()){
				RandomAccessFile o = entry.getValue();
				try {
					o.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		inputStreamRegister.clear();
		
		initChecked.set(false);
		
		fileOut = null;

	}

	public void testEnded(String host) {
	}

	public void testIterationStart(LoopIterationEvent event) {
	}

	public void testStarted() {
	}

	public void testStarted(String host) {
	}
	
	private SNMPProcessSampleResult parseSampleResultFromFile(){
		String s = null;
		SNMPProcessSampleResult res = null;
		try{
			fileIn.seek(this.offset);
			if(this.sb == null){
				sb = new StringBuffer();
			}
			while((s = fileIn.readLine()) != null){
				if(s.length() > 0 && s.contains("${cnt")){
					String r = sb.toString();
					res = (SNMPProcessSampleResult)xs.fromXML(r);
					offset=fileIn.getFilePointer();
					sb = new StringBuffer();
					return res;
				}else{
					sb.append(s);
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}
		return res;
	}
}